<?php
/**
 *
 *
 * PHP versions 5
 *
 * Copyright (C)
 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
 * of ERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software
 * Fundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
 *
 * @author Bastian Schröder <bs@chemanet.de>
 * @version 2007-02-10
 * @package rail
 */
class rail_object{

	/**
	 * Unique id for this object, generated by MySQL autoincrement colm.
	 *
	 * @var int
	 */
	private $rail_id;

	/**
	 * 9Bit binary word.
	 *
	 * @var string
	 */
	private $mode;

	/**
	 * The rail_connector has an connection to the MySQL databases alocated with this object.
	 * You can't replace it.
	 *
	 * @var rail_connector
	 */
	protected $rail_connector;

	/**
	 * Object cache for this object. Used by some methods.
	 *
	 * @var array
	 */
	protected $cache = array();

	/**
	 * Attributes which are not writeable from outside. Outside means by calling set_attribute(s) method.
	 * The array contains only the name of protected attributes not the attribute itself.
	 *
	 * @var array
	 */
	protected $protected_attributes = array( OBJ_TIME_CREATE, OBJ_TIME_LASTWRITE );


	/**
	 * Build this object from databases. Get mode and object name from databases.
	 *
	 * @exception rail_param_exception
	 * @param rail_connector $pAttribute
	 * @param int $pId
	 */
	public function __construct ( $pRailConnector, $pId ) {
		if ( !$pRailConnector instanceof rail_connector )
			throw new rail_param_exception( $this, "'pRailConnector' is not a valid rail_connector instance!" );

		$this->rail_connector = $pRailConnector;
		$this->rail_id = $pId;

		// get mode from databases
		$data = $this->rail_connector->send_request( "SELECT mode FROM objects WHERE `id`='$pId' LIMIT 1" )->fetch_assoc();
		$this->mode = $data["mode"];

		// hard get OBJ_NAME and stores to attribute cache
		$data = $this->rail_connector->send_request( "SELECT `value` FROM objects_attributes WHERE `object`='" . $this->get_id() . "' AND `attribute`='OBJ_NAME' LIMIT 1" )->fetch_assoc();
		$this->cache[ "attributes" ][OBJ_NAME] = $data[ "value" ];
	}


	/**
	 * Flush the cache.
	 */
	public function cache_flush () {
		$this->cache = array();
	}


	/**
	 * Copy object. Return copyed object.
	 *
	 * @exception rail_readpermission_exception
	 * @param rail_object $pRailObject
	 * @return rail_object
	 */
	public function copy ( $pRailObject ) {
		// check read permission
		if( !$this->is_readable( $this->rail_connector->get_user() ) )
		 	throw new rail_readpermission_exception( $this, $this->rail_connector->get_user() );

		$copy_rail_id = rail_factory::make_object($this->rail_connector, $pRailObject, get_class($this), $this->get_name() );
		$copy = rail_factory::get_object($this->rail_connector, $copy_rail_id);

		// copy all attributes not protected
		$new_attribute = array();
		foreach( $this->get_all_attributes() as $attribute => $value ){
			if( !in_array($attribute, $this->protected_attributes) )
				$new_attribute[$attribute] = $value;
		}
		$copy->set_attributes($new_attribute);

		// copy inventory
		foreach( $this->get_inventory() as $inventory ){
			if( !$inventory->is_readable( $this->rail_connector->get_user() ) )
				continue;

			$inventory->copy($copy);
		}

		return $copy;
	}


	/**
	 * Count alle objects who has this object as environment.
	 *
	 * @exception rail_readpermission_exception
	 * @param string $pRailTypes
	 * @return integer
	 */
	public function count_inventory ( $pRailTypes = "" ) {
		// check read permission
		 if( !$this->is_readable( $this->rail_connector->get_user() ) )
		 	throw new rail_readpermission_exception( $this, $this->rail_connector->get_user() );

		$sql_types = "";
		if( strlen($pRailTypes) > 0 ){
			foreach( explode("|", $pRailTypes) as $type )
				$sql_types .= " OR `type`='$type'";

			$sql_types = " AND(" . substr($sql_types, 4) . ")";
		}

		$size = 0;
		$data = $this->rail_connector->send_request( "SELECT `id` FROM objects WHERE `environment`='" . $this->get_id() . "'$sql_types" );

		while( $data_row = $data->fetch_assoc() )
			$size ++ ;

		return $size;
	}


	/**
	 * count all relations.
	 *
	 * @exception rail_readpermission_exception
	 * @return integer
	 */
	public function count_relations () {
		// check read permission
		 if( !$this->is_readable( $this->rail_connector->get_user() ) )
		 	throw new rail_readpermission_exception( $this, $this->rail_connector->get_user() );

		$size = 0;
		$data = $this->rail_connector->send_request( "SELECT type FROM objects_relations WHERE `object1`='" . $this->get_id() . "' OR `object2`='" . $this->get_id() . "'" );

		while( $data_row = $data->fetch_assoc() )
				$size ++ ;

		return $size;
	}


	/**
	 * Delete object and recrusiv the inventory.
	 *
	 * @exception rail_writepermission_exception
	 */
	public function delete () {
		// check write permission
		 if( !$this->is_writeable( $this->rail_connector->get_user() ) )
		 	throw new rail_writepermission_exception( $this, $this->rail_connector->get_user() );

		 // try to delete inventory recrusiv.
		 // If one ore more objects from inventory can't deleted, don't delete this object
		 $catchedException = false;
		 foreach( $this->get_inventory() as $object ){
		 	try{
		 		$obj_return = $object->delete();
		 	}
		 	catch( Exception $e ){
		 		$catchedException = true;
		 	}
		 }

		 if( !$catchedException ){
			 // delete all relations, attributes and the object itself
			 $this->rail_connector->send_request( "DELETE FROM objects_relations WHERE `object1`='" . $this->get_id() . "' OR `object2`='" . $this->get_id() . "'" );
			 $this->rail_connector->send_request( "DELETE FROM objects_attributes WHERE `object`='" . $this->get_id() . "'" );
			 $this->rail_connector->send_request( "DELETE FROM objects_documents WHERE `object`='" . $this->get_id() . "'" );
			 $this->rail_connector->send_request( "DELETE FROM objects WHERE `id`='" . $this->get_id() . "'" );
		 }
		 else
		 	throw new rail_writepermission_exception( $this, $this->rail_connector->get_user() );
	}


	/**
	 * Get all attributes for this object. Returned array of attributes.
	 *
	 * @exception rail_readpermission_exception
	 * @return array
	 */
	public function get_all_attributes () {
		// check read permission
		 if( !$this->is_readable( $this->rail_connector->get_user() ) )
		 	throw new rail_readpermission_exception( $this, $this->rail_connector->get_user() );

		// flush attribute cache
		unset( $this->cache[ "attributes" ] );
		$this->cache[ "attributes" ] = array();

		// get all attributes from databases
		$data = $this->rail_connector->send_request( "SELECT `attribute`,`value` FROM objects_attributes WHERE `object`='" . $this->get_id() . "' ORDER BY `attribute`" );

		// add/update all attributes to attribute cache
		while( $data_row = $data->fetch_assoc() )
			$this->cache[ "attributes" ][ $data_row[ "attribute" ] ] = $data_row[ "value" ];

		return $this->cache[ "attributes" ];
	}


	/**
	 * Get attribute, return value.
	 *
	 * @exception rail_readpermission_exception
	 * @param string $pAttribute
	 * @return string
	 */
	public function get_attribute ( $pAttribute ) {
		// check read permission
		 if( !$this->is_readable( $this->rail_connector->get_user() ) )
		 	throw new rail_readpermission_exception( $this, $this->rail_connector->get_user() );

		$this->get_attributes( array($pAttribute) );

		// check wheater attribute exist
		if( isset($this->cache[ "attributes" ][$pAttribute ]) )
			return $this->cache[ "attributes" ][$pAttribute ];

		return false;
	}


	/**
	 * Get attributes. Returned array of attributes.
	 *
	 * @exception rail_readpermission_exception
	 * @param array $pAttributes
	 * @return array
	 */
	public function get_attributes ( $pAttributes ) {
		// check read permission
		 if( !$this->is_readable( $this->rail_connector->get_user() ) )
		 	throw new rail_readpermission_exception( $this, $this->rail_connector->get_user() );

		$mysql_attributes = "";
		$requested_attributes = array();
		foreach( $pAttributes as $attribute ){
			$attribute = trim($attribute);

			// attribute found in attribute cache.
			if( isset( $this->cache[ "attributes" ][$attribute ] ) )
				$requested_attributes[ $attribute ] = $this->cache[ "attributes" ][ $attribute ];
			// otherwise add attribute to mysql select.
			else
				$mysql_attributes .= " OR `attribute`='$attribute'";
		}

		// select requested attributes from databases.
		if( strlen($mysql_attributes) > 0 ){
			$mysql_attributes = " AND (" . substr($mysql_attributes,4) . ")";
			$data = $this->rail_connector->send_request( "SELECT `attribute`,`value` FROM objects_attributes WHERE `object`='" . $this->get_id() . "'" . $mysql_attributes );

			// add selected attributes to attribute cache
			while( $data_row = $data->fetch_assoc() ){
				$this->cache[ "attributes" ][ $data_row[ "attribute" ] ] = $data_row[ "value" ];
				$requested_attributes[ $data_row[ "attribute" ] ] = $data_row[ "value" ];
			}
		}

		return $requested_attributes;
	}


	/**
	 * Get current environment (parent node).
	 * Environment is that object who has this object in his inventory.
	 *
	 * @return rail_object
	 */
	public function get_environment () {
		// get environment from databases and store to cache
		if( !isset( $this->cache[ "environment" ] ) ){
			$data = $this->rail_connector->send_request( "SELECT `environment` FROM objects WHERE `id`='" . $this->get_id() . "' LIMIT 1" )->fetch_assoc();
			$this->cache[ "environment" ] = rail_factory::get_object( $this->rail_connector, $data[ "environment" ] );
		}

		// return cached environment
		return $this->cache[ "environment" ];
	}


	/**
	 * Get rail_id.
	 *
	 * @return rail_object
	 */
	public function get_id () {
		return $this->rail_id;
	}


	/**
	 * Get alle objects who has this object as environment. Returned array of rail_object.
	 *
	 * @exception rail_readpermission_exception
	 * @param string $pRailType RAIL_TYPE|RAIL_TYPE
	 * @param string $pOrderbyAttribte
	 * @param bool $pRecursive
	 * @return array
	 */
	public function get_inventory ( $pRailTypes = "", $pOrderbyAttribte = OBJ_NAME , $pRecursive = false ) {
		// check read permission
		 if( !$this->is_readable( $this->rail_connector->get_user() ) )
		 	throw new rail_readpermission_exception( $this, $this->rail_connector->get_user() );

		$objects = array();

		$sql_types = "";
		if( strlen($pRailTypes) > 0 ){
			foreach( explode("|", $pRailTypes) as $type )
				$sql_types .= " OR `type`='$type'";

			$sql_types = " AND(" . substr($sql_types, 4) . ")";
		}

		$data = $this->rail_connector->send_request( "SELECT `id` FROM objects WHERE `environment`='" . $this->get_id() . "'$sql_types ORDER BY `id`" );

		// get all inventory objects
		while( $data_row = $data->fetch_assoc() ){
			$obj = rail_factory::get_object( $this->rail_connector,  $data_row[ "id" ] );
			$objects[] = $obj;

			if( $pRecursive )
				$objects = array_merge($objects, $obj->get_inventory( $pRailTypes, $pOrderbyAttribte, $pRecursive ) );
		}

		// sort $objects
		$objects_sorted = array();
		foreach( $objects as $object ){
			for( $i = 0; $i < count( $objects_sorted ); $i ++ ) {
				try{
					if( strtolower( $objects_sorted[ $i ]->get_attribute( $pOrderbyAttribte ) ) > strtolower( $object->get_attribute( $pOrderbyAttribte ) ) || ( strtolower( $objects_sorted[ $i ]->get_attribute( $pOrderbyAttribte ) ) == strtolower( $object->get_attribute( $pOrderbyAttribte ) ) && $objects_sorted[ $i ]->get_id() > $object->get_id() ) ){
						$tmp = 	$objects_sorted[ $i ];
						$objects_sorted[ $i ] = $object;
						$object = $tmp;
					}
				}
				catch( Exception $e ){
					// catched a read permission exception
					// means inventory object isn't readable so this object isn't sorted
				}
			}
			$objects_sorted[] = $object;
		}

		return $objects_sorted;
	}


	/**
	 * Get mode as integer.
	 *
	 * @return int
	 */
	public function get_mode () {
		return 	bindec( substr( $this->mode, 0, 3 ) ) .
				bindec( substr( $this->mode, 3, 3 ) ) .
				bindec( substr( $this->mode, 6, 3 ) );
	}


	/**
	 * Get value of attribute OBJ_NAME.
	 *
	 * @return string
	 */
	public function get_name () {
		if( !isset( $this->cache[ "attributes" ][ OBJ_NAME ] ) )
			$this->get_attributes( array( OBJ_NAME ) );
		return $this->cache[ "attributes" ][ OBJ_NAME ];
	}


	/**
	 * Get group of this object.
	 *
	 * @return rail_group
	 */
	public function get_owner_group () {
		if( !isset( $this->cache["owner_group"] ) ){
			$data = $this->rail_connector->send_request( "SELECT `group` FROM objects WHERE `id`='" . $this->get_id() . "' LIMIT 1" )->fetch_assoc();
			$this->cache["owner_group"] = rail_factory::get_object( $this->rail_connector, $data[ "group" ] );
		}

		return $this->cache["owner_group"];
	}


	/**
	 * Get user of this object.
	 *
	 * @return rail_user
	 */
	public function get_owner_user () {
		if( !isset( $this->cache["owner_user"] ) ){
			$data = $this->rail_connector->send_request( "SELECT `user` FROM objects WHERE `id`='" . $this->get_id() . "' LIMIT 1" )->fetch_assoc();
			$this->cache["owner_user"] = rail_factory::get_object( $this->rail_connector, $data[ "user" ] );
		}

		return $this->cache["owner_user"];
	}


	/**
	 * Get Path to this object.
	 *
	 * @return string
	 */
	public function get_path () {
		// if path isn't cached
		if( !isset( $this->cache[ "path" ] ) ){
			$data = $this->rail_connector->send_request( "SELECT `id` FROM objects WHERE `environment`='" . $this->get_environment()->get_id() . "' ORDER BY `id`" );
			$position = 1;
			// get all inventory objects
			while( $data_row = $data->fetch_assoc() ){
				// current rail_id
				$id = $data_row[ "id" ];

				if( $id == $this->get_id() ){
					// cached the path
					$this->cache[ "path" ] = $this->get_environment()->get_path() . urlencode( $this->get_name() ) . ( ($position > 1)?"[".$position."]":"" ) . "/";
					break;
				}

				$object = rail_factory::get_object( $this->rail_connector, $id );
				// increment position
				if( $object->get_name() == $this->get_name() )
					$position ++ ;
			}
		}

		// return cached path
		return $this->cache[ "path" ];
	}


	/**
	 * Get all relation types.
	 *
	 * @exception rail_readpermission_exception
	 * @return array
	 */
    public function get_relation_types () {
		// check read permission
		 if( !$this->is_readable( $this->rail_connector->get_user() ) )
		 	throw new rail_readpermission_exception( $this, $this->rail_connector->get_user() );

		$data = $this->rail_connector->send_request( "SELECT DISTINCT type FROM objects_relations WHERE `object1`='" . $this->get_id() . "' OR `object2`='" . $this->get_id() . "' ORDER BY type" );
		$relations = array();

		// add selected attributes to attribute cache
		while( $data_row = $data->fetch_assoc() )
			$relations[] = $data_row[ "type" ];

		return $relations;
	}


	/**
	 * Get all relations for a specifid type. Returned an array of rail_objects.
	 *
	 * @exception rail_readpermission_exception
	 * @param string $pType
	 * @return array
	 */
	public function get_relations ( $pType ) {
		// check read permission
		if( !$this->is_readable( $this->rail_connector->get_user() ) )
		 	throw new rail_readpermission_exception( $this, $this->rail_connector->get_user() );

		$type = trim( $pType );

		$objects = array();

		$data = $this->rail_connector->send_request( "SELECT `object1`,`object2` FROM objects_relations WHERE `type`='$type' AND (`object1`='" . $this->get_id() . "' OR `object2`='" . $this->get_id() . "') ORDER BY object1,object2" );
		while( $data_row = $data->fetch_assoc() ){
			$object_id	=  ($data_row[ "object1" ] == $this->get_id())?$data_row[ "object2" ]:$data_row[ "object1" ];
			$objects[] = rail_factory::get_object( $this->rail_connector, $object_id );
		}

		return $objects;
	}


	/**
	 * Move object to new environment. Use must have wrte permission on this objects as well on pRailObject.
	 *
	 * @exception rail_writepermission_exception
	 * @param rail_object $pRailObject
	 */
	public function move ( $pRailObject ) {
		 // check write permission
		 if( !$this->is_writeable( $this->rail_connector->get_user() ) )
		 	throw new rail_writepermission_exception( $this->get_path(), $this->rail_connector->get_user() );
		 // check environment write permission
		 if( !$pRailObject->is_writeable( $this->rail_connector->get_user() ) )
		 	throw new rail_writepermission_exception( $pRailObject->get_path(), $this->rail_connector->get_user() );

		$this->rail_connector->send_request( "UPDATE objects SET `environment`='" . $pRailObject->get_id() . "' WHERE `id`='" . $this->get_id() . "'" );
		$this->cache[ "environment" ] = $pRailObject;

		// update OBJ_TIME_LASTWRITE attribute
		$this->rail_connector->send_request( "UPDATE objects_attributes SET `value`='" . time() . "' WHERE `object`='" . $this->get_id() . "' AND `attribute`='OBJ_TIME_LASTWRITE'" );
	}


	/**
	 * Get all objects in this inventory matchin one ore more attribute in pAttributes the regular expression.
	 *
	 * @exception rail_readpermission_exception
	 * @param string $pRegExp
	 * @param array $pAttributes
	 * @param string $pRailTypes
	 * @param bool $pRecursive
	 * @return array
	 */
	public function inventory_search ( $pRegExp, $pAttributes, $pRailTypes = "", $pRecursive = false ){
		// check read permission
		if( !$this->is_readable( $this->rail_connector->get_user() ) )
		 	throw new rail_readpermission_exception( $this, $this->rail_connector->get_user() );

		$results = array();

		// for each readable object in inventory
		foreach( $this->get_inventory( $pRailTypes, OBJ_NAME, $pRecursive ) as $inventory ){
			if( !$inventory->is_readable( $this->rail_connector->get_user() ) )
				continue;

			// look at each needed attribute
			foreach( $pAttributes as $attribute ){
				if( preg_match($pRegExp, $inventory->get_attribute($attribute)) > 0 ){
					array_push($results, $inventory);
					continue;
				}
			}
		}

		return $results;
	}


	/**
	 * Chekced wheater an attribute is prottected.
	 *
	 * @param string $pAttribute
	 * @return bool
	 */
	public function is_attribute_protected ( $pAttribute ) {
		return !$this->is_writeable( $this->rail_connector->get_user() ) || in_array( trim($pAttribute), $this->protected_attributes );
	}


	/**
	 * Chekced wheater the object is readable for a user.
	 *
	 * @param rail_user $pRailUser
	 * @return bool
	 */
	public function is_readable ( $pRailUser ) {
		// root user has grand access
		if( $pRailUser->get_name() == "root" )
			return true;

		$user =  strrev( substr( $this->mode, 0, 3 ) );
		$group = strrev( substr( $this->mode, 3, 3 ) );
		$other = strrev( substr( $this->mode, 6, 3 ) );

		// get other rights
		if( ($other & 1) )
			return true;

		// check if this user is the primary user for specified rail_user
		if( $this->get_owner_user()->get_id() == $pRailUser->get_id() && ($user & 1) )
			return true;

		// check if this group is the primary group for specofied rail_user
		if( $pRailUser->get_owner_group()->get_id() == $this->get_owner_group()->get_id() &&  ($group & 1) )
		 	return true;

		 // check if user is in this group
		$data = $this->rail_connector->send_request( "SELECT `object1`,`object2` FROM objects_relations WHERE `type`='RAIL_MEMBERSHIP' AND `object1`='" . $this->get_owner_group()->get_id() . "' AND `object2`='" . $pRailUser->get_id() . "' LIMIT 1" )->fetch_assoc();
		if( count( $data ) > 0 &&  ($group & 1) )
			return true;

		return false;
	}


	/**
	 * Chekced wheater the object is writeable for a user.
	 *
	 * @param rail_user $pRailUser
	 * @return bool
	 */
	public function is_writeable ( $pRailUser ) {
		// root user has grand access
		if( $pRailUser->get_name() == "root" )
			return true;

		$user =  strrev( substr( $this->mode, 0, 3 ) );
		$group = strrev( substr( $this->mode, 3, 3 ) );
		$other = strrev( substr( $this->mode, 6, 3 ) );

		// get other rights
		if( ($other & 2) )
			return true;

		// check if this user is the primary user for specified rail_user
		if( $this->get_owner_user()->get_id() == $pRailUser->get_id() && ($user & 2) )
			return true;

		// check if this group is the primary group for specofied rail_user
		if( $pRailUser->get_owner_group()->get_id() == $this->get_owner_group()->get_id() &&  ($group & 2) )
		 	return true;

		// check if user is in this group
		$data = $this->rail_connector->send_request( "SELECT `object1`,`object2` FROM objects_relations WHERE `type`='RAIL_MEMBERSHIP' AND `object1`='" . $this->get_owner_group()->get_id() . "' AND `object2`='" . $pRailUser->get_id() . "' LIMIT 1" )->fetch_assoc();
		if( count( $data ) > 0 &&  ($group & 2) )
			return true;

		return false;
	}


	/**
	 * Remove one attribute.
	 *
	 * @exception rail_writepermission_exception
	 * @param string $pAttribute
	 */
	public function remove_attribute ( $pAttribute ) {
		// check write permission
		 if( !$this->is_writeable( $this->rail_connector->get_user() ) )
		 	throw new rail_writepermission_exception( $this, $this->rail_connector->get_user() );

		$attribute = trim($pAttribute);

		// cheked if attribute is protected. Protected means not removeable.
		// OBJ_NAME isn't protected but can't delete or set to empty string.
		if( in_array( $attribute, array_merge( $this->protected_attributes, array("OBJ_NAME") ) ) )
			throw new rail_writepermission_exception( "Can't delete attribute: $attribute is protected!" );

		$this->rail_connector->send_request( "DELETE FROM objects_attributes WHERE `object`='" . $this->get_id() . "' AND `attribute`='$attribute'" );
		unset(  $this->cache[ "attributes" ][ $attribute ] );

		// update OBJ_TIME_LASTWRITE attribute
		$this->rail_connector->send_request( "UPDATE objects_attributes SET `value`='" . time() . "' WHERE `object`='" . $this->get_id() . "' AND `attribute`='OBJ_TIME_LASTWRITE'" );
	}


	/**
	 * Remove one relation.
	 *
	 * @exception rail_writepermission_exception
	 * @param rail_object
	 * @param string type
	 */
	public function remove_relation ( $pRailObject, $pType ) {
		$type = trim( $pType );

		// check write permission
		if( !$this->is_writeable( $this->rail_connector->get_user() ) )
		 	throw new rail_writepermission_exception( $this, $this->rail_connector->get_user() );

		$this->rail_connector->send_request( "DELETE FROM objects_relations WHERE `type`='$type' AND (`object1`='" . $this->get_id() . "' OR `object2`='" . $this->get_id() . "') AND (`object1`='" . $pRailObject->get_id() . "' OR `object2`='" . $pRailObject->get_id() . "') LIMIT 1" );

		// update OBJ_TIME_LASTWRITE attribute
		$this->rail_connector->send_request( "UPDATE objects_attributes SET `value`='" . time() . "' WHERE `object`='" . $this->get_id() . "' AND `attribute`='OBJ_TIME_LASTWRITE'" );
	}


	/**
	 * Set attribute.
	 *
	 * @exception rail_writepermission_exception
	 * @param string $pAttributes
	 * @param string $pValue
	 */
	public function set_attribute ( $pAttribute, $pValue ) {
		 // check write permission
		 if( !$this->is_writeable( $this->rail_connector->get_user() ) )
		 	throw new rail_writepermission_exception( $this, $this->rail_connector->get_user() );

		$this->set_attributes( array( $pAttribute=>$pValue ) );
	}


	/**
	 * Set attributes.
	 *
	 * @exception rail_writepermission_exception
	 * @param array $pAttributes
	 */
	public function set_attributes ( $pAttributes ) {
		 // check write permission
		 if( !$this->is_writeable( $this->rail_connector->get_user() ) )
		 	throw new rail_writepermission_exception( $this, $this->rail_connector->get_user() );

		$forbidden_attributes = array();
		foreach( $pAttributes as $attribute=>$value ){
			$attribute =  trim($attribute);
			$value = rtrim($value);

			// cheked if attribute is protected. Protected means not writeable from outside.
			// OBJ_NAME isn't protected but can't delete or set to empty string.
			if( in_array( $attribute, $this->protected_attributes ) || ( $attribute == OBJ_NAME && strlen( $value ) == 0 ) )
				$forbidden_attributes[] = $attribute;
			// otherwise update or add attribute to databases.
			// continue, if atribute has already the same value.
			else{
				$this->get_attributes( array($attribute) );
				if( isset( $this->cache[ "attributes" ][ $attribute ] ) && $this->cache[ "attributes" ][ $attribute ] == $value )
					continue;

				// update attribute in database if exist.
				if( isset( $this->cache[ "attributes" ][ $attribute ] ) )
					$this->rail_connector->send_request( "UPDATE objects_attributes SET `value`='$value' WHERE `object`='" . $this->get_id() . "' AND `attribute`='$attribute'" );
				// otherwise add new attribute to database.
				else
					$this->rail_connector->send_request( "INSERT INTO objects_attributes (`object`,`attribute`,`value`) VALUES ('" . $this->get_id() . "','$attribute','$value')" );

				// update attribute cache
				$this->cache[ "attributes" ][ $attribute ] = $value;
				// remove chached path, if OBJ_NAME has change
				if( $attribute == "OBJ_NAME" )
					unset( $this->cache[ "path" ] );
			}
		}

		if( count($forbidden_attributes) > 0 )
			throw new rail_writepermission_exception( "Can't set attribute: " . implode( ", ", $forbidden_attributes ) . " reason protected!", 0 );

		// update OBJ_TIME_LASTWRITE attribute
		$this->rail_connector->send_request( "UPDATE objects_attributes SET `value`='" . time() . "' WHERE `object`='" . $this->get_id() . "' AND `attribute`='OBJ_TIME_LASTWRITE'" );
	}


	/**
	 * Set mode to object. Mode is given as integer.
	 *
	 * @exception rail_writepermission_exception
	 * @param integer $pMode
	 */
	public function set_mode ( $pMode ) {
		// root user has grand access
		if( $this->rail_connector->get_user()->get_name() != "root" && $this->rail_connector->get_user()->get_id() != $this->get_owner_user()->get_id() )
			throw new rail_writepermission_exception( "Only root or owner can do this!" );

		$mode = substr( decbin( substr($pMode, 0, 1) ) . "000", 0, 3 ) .
				substr( decbin( substr($pMode, 1, 1) ) . "000", 0, 3 ) .
				substr( decbin( substr($pMode, 2, 1) ) . "000", 0, 3 );

		$this->rail_connector->send_request( "UPDATE objects SET `mode`='$mode' WHERE `id`='" . $this->get_id() . "' LIMIT 1" );
		$this->mode = $mode;

		// update OBJ_TIME_LASTWRITE attribute
		$this->rail_connector->send_request( "UPDATE objects_attributes SET `value`='" . time() . "' WHERE `object`='" . $this->get_id() . "' AND `attribute`='OBJ_TIME_LASTWRITE'" );
	}


	/**
	 * Set user and group to object.
	 *
	 * @exception rail_exception|rail_writepermission_exception
	 * @param rail_user $pRailUser
	 * @param rail_group $pRailGroup
	 */
	public function set_owner ( $pRailUser = "", $pRailGroup ) {
		// root user has grand access
		if( $this->rail_connector->get_user()->get_name() != "root" && $this->rail_connector->get_user()->get_id() != $this->get_owner_user()->get_id() )
			throw new rail_writepermission_exception( "Only root or owner can do this!" );

		if( !isset($pRailGroup) || $pRailGroup instanceof rail_user )
			throw new rail_exception( "Has to set a group." );


		if( $this->rail_connector->get_user()->get_name() == "root" && $pRailUser instanceof rail_user )
			$this->rail_connector->send_request( "UPDATE objects SET `user`='" . $pRailUser->get_id() . "' WHERE `id`='" . $this->get_id() ."' LIMIT 1" );

		$this->rail_connector->send_request( "UPDATE objects SET `group`='" . $pRailGroup->get_id() . "' WHERE `id`='" . $this->get_id() ."' LIMIT 1" );

		unset( $this->cache[ "owner" ] );
		unset( $this->cache[ "group" ] );

		// update OBJ_TIME_LASTWRITE attribute
		$this->rail_connector->send_request( "UPDATE objects_attributes SET `value`='" . time() . "' WHERE `object`='" . $this->get_id() . "' AND `attribute`='OBJ_TIME_LASTWRITE'" );
	}


	/**
	 * Set relation. Returns true if a new relation is addes, false is an existing relation updated.
	 *
	 * @exception rail_writepermission_exception
	 * @param rail_object $pRailObject
	 * @param string $pType
	 */
	public function set_relation ( $pRailObject, $pType ) {
		$type = trim( $pType );

		// check write permission
		 if( !$this->is_writeable( $this->rail_connector->get_user() ) )
		 	throw new rail_writepermission_exception( $this, $this->rail_connector->get_user() );

		 // can't set constant relations
		 #if( defined( $type ) )
		 #	throw new rail_writepermission_exception( "Can't set constant type $type" );

		$data = $this->rail_connector->send_request( "SELECT `object1`,`object2` FROM objects_relations WHERE `type`='$type' AND (`object1`='" . $this->get_id() . "' OR `object2`='" . $this->get_id() . "') AND (`object1`='" . $pRailObject->get_id() . "' OR `object2`='" . $pRailObject->get_id() . "') LIMIT 1" )->fetch_assoc();

		// only add if relation dosn't exist.
		if( count( $data ) > 0 )
			throw new rail_writepermission_exception( "Can't update existing relation. Remove relation first." );

		$this->rail_connector->send_request( "INSERT INTO objects_relations (`object1`,`object2`,`type`) VALUES ('" . $this->get_id() . "','" . $pRailObject->get_id() . "','$type')" );

		// update OBJ_TIME_LASTWRITE attribute
		$this->rail_connector->send_request( "UPDATE objects_attributes SET `value`='" . time() . "' WHERE `object`='" . $this->get_id() . "' AND `attribute`='OBJ_TIME_LASTWRITE'" );
	}

} // End of rail_object
?>